package hips;

import hips.region.NewRegionListener;
import java.util.ArrayList;
import hips.region.NewRegionEvent;
import javax.swing.event.EventListenerList;

/**
 * Esta clase proporciona métodos para la construcción de una jerarquía de
 * particiones para la imagen que se especifique. En una jerarquía de
 * particiones, la imagen particionada estará dividida en regiones de pixeles.
 * Las regiones puede pertenecer a cualquiera de las particiones que pertenecen
 * a la jerarquía y pueden ser sustituidas por el cojunto de regiones que la
 * forman en la partición del nivel jerarquico anterior, o por la región que la
 * contiene en el nivel jerarquico siguiente.
 */
public class Hierarchy<PixelValue_ extends PixelValue<PixelValue_>> {
	private ArrayList<Partition<PixelValue_>> partitions;
	private Image<PixelValue_> input;
	private int[] level;
	private int regionSize;
	private EventListenerList listenerList;

	/**
	 * Inicializa una jerarquía de particiones.
	 * 
	 * @param input
	 *            Imagen a partir de la cual se va a inicializar la jerarquía de
	 *            particiones.
	 */
	public Hierarchy(Image<PixelValue_> input) {
		this.input = input;
		partitions = new ArrayList<Partition<PixelValue_>>();
		level = new int[input.getSize()];
		listenerList = new EventListenerList();
	}

	/**
	 * Añade una nueva partición a la jerarquía a partir de los parámetros que
	 * se especifican.
	 * 
	 * @param alpha
	 *            Parámetro de rango local.
	 * @param omega
	 *            Parámetro de rango global.
	 * @param cindex
	 *            Índice de conectividad.
	 * @return Devuelve la posición que ocupa la partición creada dentro de la
	 *         jerarquía, o NULL en caso de que la partición no se haya podido
	 *         añadir debido a que los parámetros especificados no permitan
	 *         situar la partición dentro del orden jerárquico actual.
	 */
	public Integer addPartition(PixelValue_ alpha, PixelValue_ omega, float cindex, int randomPixels) {
		Integer place = getPlaceForPartition(alpha, omega, cindex, randomPixels);
		if (place != null) {
			partitions.add(place, input.newPartition(Partition.IMPROVED_WITH_CINDEX, alpha, omega, cindex, randomPixels));
			Object[] listeners = listenerList.getListenerList();
			for (int i = 0; i < listeners.length; i += 2) {
				if (listeners[i] == NewRegionListener.class) {
					partitions.get(place).addNewRegionEventListener((NewRegionListener) listeners[i + 1]);
				}
			}
			partitions.get(place).makeRegions();
			if (partitions.size() == 1) {
				setPartition(0);
			} else {
				for (int i = 0; i < input.getSize(); i++) {
					if (level[i] >= place) {
						level[i]++;
					}
				}
			}
		}
		return place;
	}

	/**
	 * Examina la jerarquía actual para buscar que posición dentro de la misma
	 * ocuparía una partición caracterizada por los parámetros especificados.
	 * 
	 * @param alpha
	 *            Parámetro de rango local.
	 * @param omega
	 *            Parámetro de rango global.
	 * @param cindex
	 *            Índice de conectividad.
	 * @return Devuelve la posición que ocuparía la partición dentro de la
	 *         jerarquía, o NULL en caso de que los parámetros especificados no
	 *         permitan situar una partición dentro del orden jerárquico actual.
	 */
	public Integer getPlaceForPartition(PixelValue_ alpha, PixelValue_ omega, float cindex, int randomPixels) {
		Partition<PixelValue_> p = input.newPartition(Partition.IMPROVED_WITH_CINDEX, alpha, omega, cindex, randomPixels);
		for (int i = 0; i < partitions.size(); i++) {
			if (partitions.get(i).hierarchycallyGreaterThan(p)) {
				return i;
			}
			if (!partitions.get(i).hierarchycallyLowerThan(p)) {
				return null;
			}
		}
		return partitions.size();
	}

	/**
	 * Obtiene la región a la que pertenece <i>pixel</i> en la jerarquía actual.
	 */
	public Region getRegion(int pixel) {
		return partitions.get(level[pixel]).getRegionByPixel(pixel);
	}

	/**
	 * Fuerza a que todos los píxeles de la imagen pertenezcan a las regiones
	 * que conforma la partición del nivel <i>level</i> de la jerarquía.
	 */
	public void setPartition(int level) {
		for (int i = 0; i < input.getSize(); i++) {
			this.level[i] = level;
		}
		regionSize = partitions.get(level).getRegionSize();
		for (int i = 0; i < regionSize; i++) {
			fireRegionEvent(new NewRegionEvent(this, partitions.get(level).getRegionByLabel(i)));
		}
	}

	/**
	 * Cambia la región a la que pertenece <i>pixel</i> por la que la contiene
	 * en el nivel superior.
	 */
	public boolean increaseLevel(int pixel) {
		if (level[pixel] == partitions.size() - 1) {
			return false;
		}
		int newLevel = level[pixel] + 1;
		Region newC = partitions.get(newLevel).getRegionByPixel(pixel);
		fireRegionEvent(new NewRegionEvent(this, newC));
		int comps = 0;
		for (int i = 0; i < newC.getSize(); i++) {
			int px = newC.getPixelPosition(i);
			if (level[px] != newLevel) {
				comps++;
				Region c = getRegion(px);
				for (int j = 0; j < c.getSize(); j++) {
					level[c.getPixelPosition(j)] = newLevel;
				}
			}
		}
		regionSize -= (comps - 1);
		return true;
	}

	/**
	 * Cambia la región a la que pertenece <i>pixel</i> por la que la contiene
	 * en el nivel inferior.
	 */
	public boolean decreaseLevel(int pixel) {
		if (level[pixel] == 0) {
			return false;
		}
		int newLevel = level[pixel] - 1;
		Region c = getRegion(pixel);
		int comps = 0;
		for (int i = 0; i < c.getSize(); i++) {
			int px = c.getPixelPosition(i);
			if (level[px] != newLevel) {
				comps++;
				Region newC = partitions.get(newLevel).getRegionByPixel(px);
				fireRegionEvent(new NewRegionEvent(this, newC));
				for (int j = 0; j < newC.getSize(); j++) {
					level[newC.getPixelPosition(j)] = newLevel;
				}
			}
		}
		regionSize += (comps - 1);
		return true;
	}

	/**
	 * Obtiene la partición que está en nivel <i>level</i> de la jerarquía.
	 */
	public Partition<PixelValue_> getPartition(int level) {
		return partitions.get(level);
	}

	/**
	 * Obtiene el número de particiones de la jerarquía.
	 */
	public int getSize() {
		return partitions.size();
	}

	/**
	 * Obtiene la imagen de entrada.
	 */
	public Image<PixelValue_> getInput() {
		return input;
	}

	/**
	 * Obtiene el nivel al que pertenece el pixel especificado.
	 */
	public int getLevelId(int pixel) {
		return level[pixel];
	}

	/**
	 * Obtiene el número de regiones de la jerarquía.
	 */
	public int getRegionsSize() {
		return regionSize;
	}

	/**
	 * añade un listener que escuchará los eventos relacionados con los cambios
	 * producidos en las regiones, debidos a la llamada a las funciones
	 * <i>increaseLevel</i>, <i>decreaseLevel</i> y <i>addPartition</i>.
	 */
	public void addNewRegionEventListener(NewRegionListener listener) {
		listenerList.add(NewRegionListener.class, listener);
	}

	/**
	 * borra el listener si fué añadido anteriormente.
	 */
	public void removeNewRegionEventListener(NewRegionListener listener) {
		listenerList.remove(NewRegionListener.class, listener);
	}

	private void fireRegionEvent(NewRegionEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == NewRegionListener.class) {
				((NewRegionListener) listeners[i + 1]).newRegionCreated(evt);
			}
		}
	}
}
